/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.droidcon.apps.template.io.json;

import static org.droidcon.apps.template.util.ParserUtils.sanitizeId;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;

import org.droidcon.apps.template.io.XmlHandler.HandlerException;
import org.droidcon.apps.template.provider.ScheduleContract;
import org.droidcon.apps.template.provider.ScheduleContract.Sessions;
import org.droidcon.apps.template.provider.ScheduleContract.Speakers;
import org.droidcon.apps.template.provider.ScheduleContract.SyncColumns;
import org.droidcon.apps.template.provider.ScheduleDatabase.SessionsSpeakers;
import org.droidcon.apps.template.provider.ScheduleDatabase.SessionsTracks;
import org.droidcon.apps.template.util.Lists;
import org.droidcon.apps.template.util.ParserUtils;
import org.xmlpull.v1.XmlPullParser;

import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.text.Html;
import android.text.format.Time;
import android.util.Log;

import com.google.gson.stream.JsonReader;

/**
 * Handle a remote {@link XmlPullParser} that defines a set of {@link Sessions}
 * entries. Assumes that the remote source is a Google Spreadsheet.
 */
public class RemoteBarcampSessionsHandler extends JsonHandler {
	private static final String TAG = "SessionsHandler";

	/**
	 * Custom format used internally that matches expected concatenation of
	 * {@link Columns#SESSION_DATE} and {@link Columns#SESSION_TIME}.
	 */
	private static final SimpleDateFormat sTimeFormat = new SimpleDateFormat(
			"dd MM yyyy HH.mm Z", Locale.US);

	private static final boolean DEBUG = true;

	public RemoteBarcampSessionsHandler() {
		super(ScheduleContract.CONTENT_AUTHORITY);
	}

	/** {@inheritDoc} */
	@Override
	public ArrayList<ContentProviderOperation> parse(JsonReader reader,
			ContentResolver resolver) throws IOException {
		final ArrayList<ContentProviderOperation> batch = Lists.newArrayList();

		reader.beginObject();

		// nodes
		reader.nextName();

		reader.beginArray();
		while (reader.hasNext()) {

			reader.beginObject();

			// node
			reader.nextName();
			reader.beginObject();

			String nid = null;
			String title = null;
			String timeslot = null;
			String track = null;
			String roomId = null;
			String lastEdit = null;
			String sessionAbstract = null;
			String authorCompany = null;
			String authorFirstName = null;
			String authorLastName = null;
			while (reader.hasNext()) {
				String name = reader.nextName();
				if (DEBUG){
					Log.v(TAG, name);
				}
				if (name.equals("nid")) {					
					nid = reader.nextString();
				} else if (name.equals("title")) {
					title = Html.fromHtml(reader.nextString()).toString();
				} else if (name.equals("Timeslot")) {
					timeslot = reader.nextString();
				} else if (name.equals("abstract")) {
					sessionAbstract = Html.fromHtml(reader.nextString())
							.toString();
				} else if (name.equals("Author Company")) {
					authorCompany = reader.nextString();
				} else if (name.equals("Author Firstname")) {
					authorFirstName = reader.nextString();
				} else if (name.equals("Author Lastname")) {
					authorLastName = reader.nextString();
				} else if (name.equals("Room")) {
					roomId = reader.nextString().trim();
				} else if (name.equals("changed")) {
					lastEdit = reader.nextString();
				} else {
					reader.skipValue();
				}
			}
			reader.endObject();
			reader.endObject();

			//
			// add speaker
			//
			String uid = String.valueOf(new StringBuilder().append(authorFirstName)
					.append(authorLastName).append(authorCompany).toString().hashCode());
			final String speakerId = sanitizeId(uid, true);
			final Uri speakerUri = Speakers.buildSpeakerUri(speakerId);

			// Check for existing details, only update when changed
			final long serverUpdated = ParserUtils.parseTimeJson(lastEdit);

			// Clear any existing values for this speaker, treating the
			// incoming details as authoritative.
			batch.add(ContentProviderOperation.newDelete(speakerUri).build());

			final ContentProviderOperation.Builder builder2 = ContentProviderOperation
					.newInsert(Speakers.CONTENT_URI);

			builder2.withValue(SyncColumns.UPDATED, serverUpdated);
			builder2.withValue(Speakers.SPEAKER_ID, speakerId);
			builder2.withValue(Speakers.SPEAKER_NAME, authorFirstName + " "
					+ authorLastName);
			builder2.withValue(Speakers.SPEAKER_COMPANY, authorCompany);
			// builder.withValue(Speakers.SPEAKER_URL,
			// entry.get(Columns.SPEAKER_URL));
			if (DEBUG) {
				Log.v(TAG, speakerId);
			}
			// Normal speaker details ready, write to provider
			batch.add(builder2.build());

			//
			// add session
			//
			track = "Barcamp";
			final String sessionId = sanitizeId(nid);
			final Uri sessionUri = Sessions.buildSessionUri(sessionId);

			// Check for existing details, only update when changed
			final ContentValues values = querySessionDetails(sessionUri,
					resolver);
			final long localUpdated = values.getAsLong(SyncColumns.UPDATED);

			if (DEBUG) {
				Log.v(TAG, "found session " + nid + "/" + title);
				Log.v(TAG, "found localUpdated=" + localUpdated + ", server="
						+ serverUpdated);
			}
			// if (localUpdated >= serverUpdated)
			// continue;

			final Uri sessionTracksUri = Sessions.buildTracksDirUri(sessionId);
			final Uri sessionSpeakersUri = Sessions
					.buildSpeakersDirUri(sessionId);

			// Clear any existing values for this session, treating the
			// incoming details as authoritative.
			batch.add(ContentProviderOperation.newDelete(sessionUri).build());
			batch.add(ContentProviderOperation.newDelete(sessionTracksUri)
					.build());
			batch.add(ContentProviderOperation.newDelete(sessionSpeakersUri)
					.build());

			final ContentProviderOperation.Builder builder = ContentProviderOperation
					.newInsert(Sessions.CONTENT_URI);

			builder.withValue(SyncColumns.UPDATED, serverUpdated);
			builder.withValue(Sessions.SESSION_ID, sessionId);

			builder.withValue(Sessions.SESSION_TITLE, title);
			builder.withValue(Sessions.SESSION_ABSTRACT, sessionAbstract);

			if (timeslot == null) {
				timeslot = "09.30 - 11.00";
			}
			final int timeSplit = timeslot.indexOf("-");
			if (timeSplit == -1) {
				throw new HandlerException("Expecting " + Columns.SESSION_TIME
						+ " to express span");
			}

			long startTime = parseTime(timeslot.substring(0, 5));
			long endTime = parseTime(timeslot.substring(8, 13));
			final String blockId = ParserUtils.findOrCreateBlock(
					ParserUtils.BLOCK_TITLE_BREAKOUT_SESSIONS,
					ParserUtils.BLOCK_TYPE_SESSION, startTime, endTime, batch,
					resolver);

			builder.withValue(Sessions.BLOCK_ID, blockId);
			if (roomId == null) {
				roomId = "tbc";
			}
			builder.withValue(Sessions.ROOM_ID, roomId);

			// Inherit starred value from previous row
			if (values.containsKey(Sessions.SESSION_STARRED)) {
				builder.withValue(Sessions.SESSION_STARRED,
						values.getAsInteger(Sessions.SESSION_STARRED));
			}

			// Normal session details ready, write to provider
			batch.add(builder.build());

			// Assign track
			final String trackId = sanitizeId(track);
			batch.add(ContentProviderOperation.newInsert(sessionTracksUri)
					.withValue(SessionsTracks.SESSION_ID, sessionId)
					.withValue(SessionsTracks.TRACK_ID, trackId).build());

			// Assign speakers
			batch.add(ContentProviderOperation.newInsert(sessionSpeakersUri)
					.withValue(SessionsSpeakers.SESSION_ID, sessionId)
					.withValue(SessionsSpeakers.SPEAKER_ID, uid).build());

		}

		reader.endArray();

		return batch;
	}

	/**
	 * Parse the given date and time coming from spreadsheet. This is tightly
	 * tied to a specific format. Ideally, if the source used use RFC 3339 we
	 * could parse quickly using {@link Time#parse3339}.
	 * <p>
	 * Internally assumes PST time zone and year 2011.
	 * 
	 * @param date
	 *            String of format "Wednesday May 19", usually read from
	 *            {@link Columns#SESSION_DATE}.
	 */
	private static long parseTime(String time) throws HandlerException {
		final String composed = String.format("13 03 2012 %s +0100", time);
		try {
			Date d = sTimeFormat.parse(composed);
			if (DEBUG) {
				Log.v(TAG, d.toString());
			}
			return d.getTime();
		} catch (java.text.ParseException e) {
			throw new HandlerException("Problem parsing timestamp", e);
		}
	}

	private static ContentValues querySessionDetails(Uri uri,
			ContentResolver resolver) {
		final ContentValues values = new ContentValues();
		final Cursor cursor = resolver.query(uri, SessionsQuery.PROJECTION,
				null, null, null);
		try {
			if (cursor.moveToFirst()) {
				values.put(SyncColumns.UPDATED,
						cursor.getLong(SessionsQuery.UPDATED));
				values.put(Sessions.SESSION_STARRED,
						cursor.getInt(SessionsQuery.STARRED));
			} else {
				values.put(SyncColumns.UPDATED, ScheduleContract.UPDATED_NEVER);
			}
		} finally {
			cursor.close();
		}
		return values;
	}

	private interface SessionsQuery {
		String[] PROJECTION = { SyncColumns.UPDATED, Sessions.SESSION_STARRED, };

		int UPDATED = 0;
		int STARRED = 1;
	}

	/** Columns coming from remote spreadsheet. */
	private interface Columns {
		String SESSION_DATE = "sessiondate";
		String SESSION_TIME = "sessiontime";
		String SESSION_ROOM = "sessionroom";
		String SESSION_TRACK = "sessiontrack";
		String SESSION_LEVEL = "sessionlevel";
		String SESSION_TITLE = "sessiontitle";
		String SESSION_TAGS = "sessiontags";
		String SESSION_HASHTAG = "sessionhashtag";
		String SESSION_SLUG = "sessionslug";
		String SESSION_SPEAKERS = "sessionspeakers";
		String SESSION_ABSTRACT = "sessionabstract";
		String SESSION_REQUIREMENTS = "sessionrequirements";
		String SESSION_URL = "sessionurl";
		String SESSION_MODERATOR_URL = "sessionmoderatorurl";
		String SESSION_YOUTUBE_URL = "sessionyoutubeurl";
		String SESSION_PDF_URL = "sessionpdfurl";
		String SESSION_FEEDBACK_URL = "sessionfeedbackurl";
		String SESSION_NOTES_URL = "sessionnotesurl";

		// session_date: Wednesday May 19
		// session_time: 10:45am-11:45am
		// session_room: 6
		// session_track: Enterprise, App Engine
		// session_level: 201
		// session_title: Run corporate applications on Google App Engine? Yes
		// we do.
		// session_slug: run-corporate-applications
		// session_tags: Enterprise, SaaS, PaaS, Hosting, App Engine, Java
		// session_speakers: Ben Fried, John Smith
		// session_abstract: And you can too! Come hear Google's CIO Ben Fried
		// describe...
		// session_requirements: None
		// session_url: http://www.google.com/events/io/2011/foo
		// session_hashtag: #io11android1
		// session_youtube_url
		// session_pdf_url
		// session_feedback_url
		// session_moderator_url
		// session_notes_url
	}

}
